목에 대해 리팩터링 내성과 회귀 방지를 최대화해서 최대 가치의 통합 테스트를 개발하는 데 도움이 되는 지침을 알아본다.
📖 10.1 데이터베이스 테스트를 위한 전제 조건
🔖 10.1.1 데이터베이스를 형상 관리 시스템에 유지
데이터베이스 스키마를 일반 코드로 취급하는 것이 첫 번째 단계다
- 일반 코드와 마찬가지로 형상 관리 시스템에 저장하는 것이 최선
모델 데이터베이스를 사용하는 것은 데이터베이스 스키마를 유지하는 데 상당히 좋지 못한 방법이다.
- 변경 내역 부재
- 복수의 원천 정보
🔖 10.1.2 참조 데이터도 데이터베이스 스키마다
애플리케이션이 데이터를 수정할 수 있으면 일반 데이터고, 그렇지 않으면 참조 데이터다.
참조 데이터는 애플리케이션의 필수 사항이므로, 테이블, 뷰 그리고 다른 데이터베이스 스키마와 함께 형상관리 시스템에 저장해야 한다.
🔖 10.1.3 모든 개발자를 위한 별도의 데이터베이스 인스턴스
공유 데이터베이스를 사용하면 개발 프로세스를 방해하게 된다.
- 서로 다른 개발자가 실행한 테스트는 서로 간섭되기 때문이다.
- 하위 호환성이 없는 변경으로 다른 개발자의 작업을 막을 수 있기 때문이다.
🔖 10.1.4 상태 기반 데이터베이스 배포와 마이그레이션 기반 데이터베이스 배포
- 상태 기반 방식은 상태를 형상 관리에 저장함으로써 상태를 명시하고 비교 도구가 마이그레이션을 암묵적으로 제어할 수 있게 한다.
- 마이그레이션 기반 방식은 마이그레이션을 명시적으로 하지만 상태를 암묵적으로 둔다. 데이터베이스 상태를 직접 볼 수 없으며 마이그레이션으로 조합해야 한다.
📖 10.2 데이터베이스 트랜잭션 관리
🔖 10.2.1 제품 코드에서 데이터베이스 트랜잭션 관리하기
잠재적인 모순을 피하려면 결정 유형을 두 가지로 나눠야 한다.
- 업데이트할 데이터
- 업데이트 유지 또는 롤백 여부
Database 클래스를 리포지터리와 트랜잭션으로 나눠서 이러한 책임을 구분할 수 있다.
- 리포지터리는 데이터베이스의 데이터에 대한 접근과 수정을 가능하게 하는 클래스
- 트랜잭션은 데이터 업데이트를 완전히 커밋하거나 롤백하는 클래스
🔖 10.2.2 통합 테스트에서 데이터베이스 트랜잭션 관리하기
통합 테스트에서 테스트 구절 간에 데이터베이스 트랜잭션이나 작업 단위를 재사용하지 말라.
- 통합 테스트에서 적어도 세 개의 트랜잭션 또는 작업 단위를 사용하라
- 준비, 실행, 검증 구절당 하나씩
📖 10.3 테스트 데이터 생명 주기
🔖 10.3.1 병렬 테스트 실행과 순차적 테스트 실행
통합 테스트를 병렬로 실행하려면 상당한 노력이 필요하다.
- 성능 향상을 위해 시간을 허비하지 말고 순차적으로 통합 테스트를 실행하는 것이 더 실용적이다.
- 통합 테스트의 실행 시간을 최소화해야 하는 경우가 아니라면 컨테이너를 사용하지 않는 것이 좋다.
🔖 10.3.2 테스트 실행 간 데이터 정리
테스트 실행 간 데이터를 정리하는 방법은 네 가지가 있다.
- 각 테스트 전에 데이터베이스 백업 복원하기
- 테스트 종료 시점에 데이터 정리하기
- 데이터베이스 트랜잭션에 각 테스트를 래핑하고 커밋하지 않기
- 테스트 시작 시점에 데이터 정리하기
- 이 방법이 가장 좋다.
🔖 10.3.3 인메모리 데이터베이스 피하기
인메모리 DB의 장점
- 테스트 데이터를 제거할 필요가 없음
- 작업 속도 향상
- 테스트가 실행될 때마다 인스턴스화 가능
위와 같은 장점에도 불구하고, 기능적으로 일관성이 없기 때문에 사용하지 않는 것이 좋다.
📖 10.4 테스트 구절에서 코드 재사용하기
통합 테스트를 짧게 하기에 가장 좋은 방법은 비즈니스와 관련이 없는 기술적인 부분을 비공개 메서드나 헬퍼 클래스로 추출하는 것
🔖 10.4.1 준비 구절에서 코드 재사용하기
테스트 준비 구절 간에 코드를 재사용하기에 가장 좋은 방법은 비공개 팩토리 메서드를 도입하는 것
- 기본값을 사용하면 인수를 선택적으로 지정
- 선택적 인수를 사용하면 어떤 인수가 테스트 시나리오와 관련이 있는지도 강조할 수 있음.
팩토리 메서드의 위치는 동일한 클래스에 배치
- 코드 복제가 중요한 문제가 될 경우에만 별도의 헬퍼 클래스로 이동
- 기초 클래스에 팩토리 메서드를 넣지 말라.
- 기초 클래스는 데이터 정리와 같이 모든 테스트에서 실행해야 하는 코드를 위한 클래스
🔖 10.4.2 실행 구절에서 코드 재사용하기
decorator 메서드를 사용하면 테스트의 실행 구절은 몇 줄만으로 충분하다.
- 어떤 컨트롤러 기능을 호출해야 하는지에 대한 정보가 있는 대리자(delegate)를 받는 메서드를 도입
🔖 10.4.3 검증 구절에서 코드 재사용하기
- 헬퍼 메서드 사용
- 플루언트 인터페이스 활용
🔖 10.4.4 테스트가 데이터베이스 트랜잭션을 너무 많이 생성하는가?
유지 보수성을 위해 성능을 양보함으로써 절충하는 것이 좋다.
- 빠른 피드백과 유지 보수성 간의 절충
📖 10.5 데이터베이스 테스트에 대한 일반적인 질문
🔖 10.5.1 읽기 테스트를 해야 하는가?
쓰기는 위험성이 높기 때문에 철저히 테스트하는 것이 매우 중요하다.
그러나 읽기는 가장 복잡하거나 중요한 읽기 작업만 테스트하고, 나머지는 무시하라.
- 읽기를 테스트하는 경우에는 성능 면에서 ORM보다 우수한 일반 SQL을 사용하는 것이 좋다
🔖 10.5.2 리포지터리 테스트를 해야 하는가?
리포지터리는 복잡도가 거의 없고 프로세스 외부 의존성인 데이터베이스와 통신한다.
- 외부 의존성이 있으므로 유지비⬆️
- 테스트하기 가장 좋은 방법은 약간의 복잡도를 별도의 알고리즘으로 추춭하고 해당 알고리즘 전용 테스트를 작성
리포지터리는 직접 테스트하지 말고, 포괄적인 통합 테스트 스위트의 일부로 취급하라.
📖 10.6 결론
데이터베이스 테스트를 잘 만들면 버그로부터 훌륭히 보호할 수 있다.
- 특히, ORM 전환 혹은 DB 공급업체 변경에 큰 도움이 된다.